package be.selckin.swu.debug; import be.selckin.swu.model.DiscardingModel; import com.google.common.collect.Lists; import org.apache.wicket.Component; import org.apache.wicket.MarkupContainer; import org.apache.wicket.model.IModel; import org.apache.wicket.model.LoadableDetachableModel; import org.apache.wicket.request.component.IRequestablePage; import org.apache.wicket.request.cycle.AbstractRequestCycleListener; import org.apache.wicket.request.cycle.RequestCycle; import org.apache.wicket.util.visit.IVisit; import org.apache.wicket.util.visit.IVisitor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.lang.reflect.Field; import java.util.List; public class ModelDetachedChecker extends AbstractRequestCycleListener { private static final Logger log = LoggerFactory.getLogger(ModelDetachedChecker.class); @Override public void onDetach(RequestCycle cycle) { super.onDetach(cycle); checkPage(RequestCycle.get().getMetaData(PageTrackingRequestCycleListener.REQUESTED_PAGE)); checkPage(RequestCycle.get().getMetaData(PageTrackingRequestCycleListener.RESPONSE_PAGE)); } private void checkPage(IRequestablePage requestablePage) { if (requestablePage instanceof MarkupContainer) { ((MarkupContainer) requestablePage).visitChildren(new IVisitor<Component, Object>() { @Override public void component(Component object, IVisit<Object> visit) { checkFieldsForModels(object, object); } }); } } private void checkFieldsForModels(Component component, Object object) { for (Field field : getAllFields(object)) { try { Object value = field.get(object); if (value != null) { if (value.getClass().isArray()) { for (Object arrayEl : (Object[]) value) { if (arrayEl instanceof IModel) checkModel(component, arrayEl); } } else if (value instanceof IModel) { checkModel(component, value); } } } catch (IllegalAccessException e) { log.error("failed", e); } } } private void checkModel(Component component, Object model) { if (model instanceof LoadableDetachableModel) { if (checkFieldIsNull("transientModelObject", model, component)) checkBooleanIsFalse("attached", model, component); } if (model instanceof DiscardingModel) { checkFieldIsNull("obj", model, component); } log.debug("found model {}", model); checkFieldsForModels(component, model); } private boolean checkBooleanIsFalse(String name, Object model, Component component) { Field field = getField(name, model); if (field != null) { try { if (field.getBoolean(model)) { onFoundNonDetached(model, component); return false; } } catch (IllegalAccessException e) { log.error("failed", e); } } else { log.error("Could not find field {} in {}", name, model); } return true; } private void onFoundNonDetached(Object model, Component component) { log.warn("Found non detached model: {} in {}", model, component); } private boolean checkFieldIsNull(String name, Object model, Component component) { Field field = getField(name, model); if (field != null) { try { if (field.get(model) != null) { onFoundNonDetached(model, component); return false; } } catch (IllegalAccessException e) { log.error("failed", e); } } else { log.error("Could not find field {} in {}", name, model); } return true; } private Field getField(String name, Object obj) { for (Field field : getAllFields(obj)) if (field.getName().equalsIgnoreCase(name)) return field; return null; } private Iterable<Field> getAllFields(Object obj) { List<Field> fields = Lists.newArrayList(); Class<?> current = obj.getClass(); while (current != null && current != Object.class) { for (Field field : current.getDeclaredFields()) { if (!field.isSynthetic()) { if (!field.isAccessible()) field.setAccessible(true); fields.add(field); } } current = current.getSuperclass(); } return fields; } }